package net.mujiwulian.utils;


import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClaims;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.mujiwulian.enums.RedisKey;
import net.mujiwulian.view.UserSimple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author silence
 * @Description: jwt工具类
 */
@Slf4j
@Component("jwtTokenUtil")
public class JwtTokenUtil implements Serializable {
    private static final String CLAIM_KEY_USER = "sub";
    private static final String CLAIM_KEY_IP = "ip";
    private static final long serialVersionUID = -8305152446124853696L;
    /**
     * 密钥
     */
    private final static String BASE64_SECRET = "%^&*()draw@#$%^&*!~sauna";
    /**
     * token有效期 30分钟
     */
    private final static Long EXPIRATION = 30 * 60 * 1000L;
    /**
     * token有效时间不足5分钟就刷新
     */
    private final static Long REFRESH_TIME = 5 * 60 * 1000L;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 创建token
     *
     * @param claims 内容
     * @return token
     */
    private String createToken(Claims claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION);
        String token = Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, BASE64_SECRET).compact();
        String username = claims.getId();
        redisTemplate.opsForValue().set(RedisKey.TOKEN.value() + username, token, EXPIRATION, TimeUnit.MILLISECONDS);
        return token;
    }

    /**
     * 添加令牌
     *
     * @param userDetails 主体
     * @param ip          请求ip
     * @return jwt
     */
    public String generateToken(UserDetails userDetails, String ip) {
        Claims claims = new DefaultClaims();
        claims.setId(userDetails.getUsername());
        claims.put(CLAIM_KEY_USER, userDetails);
        claims.put(CLAIM_KEY_IP, ip);
        return createToken(claims);
    }

    /**
     * 验证令牌
     *
     * @param token      请求token
     * @param redisToken redis中的token
     * @param requestIp  这次请求的ip
     * @return 如果token有效且有效期不足5分钟就返回新的token，否则返回false或true
     */
    public CheckResult<String> validateToken(Claims claims, String token, String redisToken, String requestIp) {
        try {
            if (!token.equals(redisToken)) {
                log.warn("本次请求的token与redis中存储的token不一致");
                return new CheckResult<>(false, null);
            }
            Date expiration = claims.getExpiration();
            String tokenIp = claims.get(CLAIM_KEY_IP, String.class);
            log.debug("token中的ip：" + tokenIp);
            log.debug("本次请求的ip：" + requestIp);
            if (tokenIp.equals(requestIp)) {
                log.debug("token过期时间: " + expiration + " ---- 剩余有效时间: " + ((expiration.getTime() - System.currentTimeMillis()) / 1000) + "s");
                if (new Date(System.currentTimeMillis() + REFRESH_TIME).after(expiration)) {
                    return new CheckResult<>(true, createToken(claims));
                } else {
                    return new CheckResult<>(true, null);
                }
            } else {
                log.warn("ip不匹配");
                return new CheckResult<>(false, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("token解析失败");
            return new CheckResult<>(false, null);
        }
    }

    /**
     * 获取用户信息
     *
     * @param claims claims
     * @return 用户信息
     */
    public UserDetails getUser(Claims claims) {
        return JSON.parseObject(JSON.toJSONString(claims.get(CLAIM_KEY_USER)), UserSimple.class);
    }

    /**
     * 判断令牌是否过期
     *
     * @param token token
     * @return 是否过期
     */
    public CheckResult<Claims> isExpired(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(BASE64_SECRET).parseClaimsJws(token).getBody();
            Date expiration = claims.getExpiration();
            log.debug("token过期时间: " + expiration + " ---- 剩余有效时间: " + ((expiration.getTime() - System.currentTimeMillis()) / 1000) + "s");
            return new CheckResult<>(expiration.after(new Date()), claims);
        } catch (Exception e) {
            return new CheckResult<>(false, null);
        }
    }


    /**
     * 移除令牌
     *
     * @param authHeader 请求头
     */
    public void removeToken(String authHeader) {
        String tokenHead = "Bearer:";
        if (authHeader != null && authHeader.startsWith(tokenHead)) {
            String token = authHeader.substring(tokenHead.length());
            CheckResult<Claims> checkResult = isExpired(token);
            if (checkResult.success) {
                redisTemplate.delete(RedisKey.TOKEN.value() + getUser(checkResult.data).getUsername());
            }
        }
    }


    @Data
    @AllArgsConstructor
    public static class CheckResult<T> {
        boolean success;
        T data;
    }


}
